feat(open): add 'open vscode' and 'open ssms', remove retired 'open ads'#688
feat(open): add 'open vscode' and 'open ssms', remove retired 'open ads'#688dlevy-msft-sql wants to merge 46 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds support for opening Visual Studio Code and SQL Server Management Studio (SSMS) to work with SQL Server connections managed by sqlcmd, addressing the deprecation of Azure Data Studio. The implementation uses clipboard-based password sharing since both tools use sandboxed credential storage.
Changes:
- Adds
sqlcmd open vscodecommand that creates connection profiles in VS Code settings and auto-installs the MSSQL extension - Adds
sqlcmd open ssmscommand (Windows-only) that launches SSMS with pre-configured connection parameters - Implements cross-platform clipboard support for secure password sharing
- Includes comprehensive tests and documentation updates
Reviewed changes
Copilot reviewed 30 out of 31 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
cmd/modern/root/open.go |
Updates open command to include VSCode and SSMS subcommands |
cmd/modern/root/open/vscode.go |
Main VSCode command implementation with settings file manipulation |
cmd/modern/root/open/vscode_*.go |
Platform-specific VSCode implementations |
cmd/modern/root/open/ssms.go |
Main SSMS command implementation |
cmd/modern/root/open/ssms_*.go |
Platform-specific SSMS implementations (Fatal on non-Windows) |
cmd/modern/root/open/clipboard.go |
Helper function for clipboard-based password sharing |
cmd/modern/root/open/*_test.go |
Comprehensive test coverage for new commands |
internal/tools/tool/vscode*.go |
VSCode tool detection and configuration |
internal/tools/tool/ssms*.go |
SSMS tool detection and configuration |
internal/pal/clipboard*.go |
Cross-platform clipboard implementation using native APIs |
internal/tools/tools.go |
Registers VSCode and SSMS tools |
README.md |
Adds clear documentation with usage examples |
.gitignore |
Adds /modern build artifact |
The MSSQL extension expects the connection profile server field in 'host,port' grammar (matching .devcontainer/devcontainer.json), not the 'tcp:host,port' form sqlcmd/go-mssqldb uses. Prefixing with 'tcp:' prevented the extension from matching the saved profile when launched via the vscode://ms-mssql.mssql/connect URI. Drop the prefix in both createProfile and mssqlConnectURI. Also fix TestVSCodeCreateProfile, which stored the password as plain text. config.GetCurrentContextInfo() runs the stored value through secret.Decode, so the test panicked once createProfile started reading the password. Encode with secret.Encode and set PasswordEncryption='none' to match.
Address PR microsoft#688 review feedback: - Add 'sqlcmd open vscode' to root help example chain (was Windows-only via ssms). - In 'sqlcmd config add-context' next-step hints, include 'Open in SQL Server Management Studio' only on Windows; always include 'Open in Visual Studio Code'. - Match ssms Short text on Windows and non-Windows builds; both now say '(Windows only)' so users on macOS/Linux understand why 'sqlcmd open ssms --help' shows up alongside vscode. - Remove /modern from .gitignore. No build path produces a 'modern' binary at the repo root; the rule was dead.
Add TestSSMSNotInstalledWhenSsmsExeMissing: when vswhere reports an install root but Common7\IDE\Ssms.exe is absent (corrupt install, or another VS product registered under the same productID), IsInstalled must return false. This was the only gap in ssms_windows_test.go alongside the existing Windows-only coverage in vswhere_windows_test.go (version range pinning, latest fallback, multi-line output, exec failure, missing vswhere.exe). Delete the cross-platform ssms_test.go. Its sole assertion was that Name() returns 'ssms', which is set by a literal string in Init() and tests nothing about the SSMS discovery or launch behavior that actually matters.
Replace the strip-comments-then-marshal round trip (which silently lost user comments and trailing commas, requiring a .sqlcmd-backup file written alongside settings.json) with a hujson AST patch. We parse the original document, build an RFC 6902 patch that adds or replaces only the two keys we own (mssql.connections, mssql.connectionGroups), and pack the modified AST. Comments, trailing commas, and unrelated user settings round-trip untouched on replace operations. Drops the multi-backup hazard raised in PR microsoft#688: the previous code overwrote settings.json.sqlcmd-backup on every invocation, so a second container creation lost the original. With comments preserved, no backup file is created. Full AST-level preservation across additions and per-connection comments is tracked in microsoft#767.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 60 out of 72 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
internal/tools/tool/tool_test.go:107
- TestRun starts an interactive shell (cmd.exe) with arbitrary args and tool.Run now detaches the process, so on Windows this can leave a stray cmd.exe running (and on non-Windows COMSPEC is typically unset). Make the test run an OS-appropriate command that exits immediately (e.g.,
cmd.exe /c exit 0or/bin/sh -c true) so the test is deterministic and doesn’t spawn long-lived processes.
Replace the strip-comments-then-marshal round trip (which silently lost user comments and trailing commas, requiring a .sqlcmd-backup file written alongside settings.json) with a hujson AST patch. We parse the original document, build an RFC 6902 patch that adds or replaces only the two keys we own (mssql.connections, mssql.connectionGroups), and pack the modified AST. Comments, trailing commas, and unrelated user settings round-trip untouched on replace operations. Drops the multi-backup hazard raised in PR microsoft#688: the previous code overwrote settings.json.sqlcmd-backup on every invocation, so a second container creation lost the original. With comments preserved, no backup file is created. Full AST-level preservation across additions and per-connection comments is tracked in microsoft#767.
VS Code tooling already has full Linux support (vscode_linux.go); ssms on non-Windows fatals with a clear message via ssms_unix.go. Drops the stuartpa TODO gate so 'sqlcmd open vscode' works on Linux.
Open compiled on windows/darwin/linux, which are the only platforms this repo builds for (see build/arch.txt), so the runtime.GOOS check around appending Open to subCommands was dead code. Drop the gate and add a regression test that asserts open is registered.
Three Linux fixes to settings.json path resolution: * Snap-installed VS Code is sandboxed; writing to ~/.config/Code/User has no effect on what the snap actually reads. When the resolved vscode launcher lives under /snap/, write to ~/snap/code/current/.config/Code/User (or code-insiders for the insiders build) instead. * Honor XDG_CONFIG_HOME on Linux for non-snap installs, falling back to ~/.config. * When the home directory cannot be resolved, fail loudly via Output().FatalWithHintExamples instead of silently writing settings.json under the current working directory. Adds ExePath() to the tool type so the open command can inspect the resolved launcher, plus a table-driven test for the pure linuxVSCodeConfigDir helper covering stable, insiders, XDG override, and both snap builds.
…all-success path Two security cleanups from the latest Copilot review: * createProfile now only writes savePassword/password into VS Code settings.json when the connection is local (i.e. a sqlcmd-managed container). For remote SQL servers, let the mssql extension prompt and store credentials in the OS credential store instead of having sqlcmd persist plaintext into settings. * launchSsms now resolves and IsInstalled-checks the SSMS tool before copying the password to the clipboard. Previously a missing-SSMS fatal would still leave the password on the clipboard. Adds a regression test (TestVSCodeCreateProfileRemoteDoesNotPersistPassword) covering the remote SqlLogin case.
- vscode: use existing localized 'Failed to encode VS Code settings' key - ssms tool: actually inherit parent stdio in fallback (comment was wrong) - vswhere: short-circuit to empty on non-integer version (was returning arbitrary instance) - readme: clarify ssms clipboard handoff only applies to SQL authentication
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 61 out of 73 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
internal/tools/tool/tool_test.go:106
- TestRun currently uses COMSPEC unconditionally and only skips on linux. COMSPEC is typically unset on non-Windows, and the skip message is also outdated now that tool_linux.go implements generateCommandLine. This makes the test unreliable/cross-platform incorrect.
…pty id Match the documented contract instead of panicking. Callers like the vscode launcher inspect optional container IDs and rely on the empty-string fallback.
On Windows, suggest USERPROFILE instead of HOME since that's what os.UserHomeDir consults.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 61 out of 73 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
internal/tools/tool/tool_test.go:106
TestRunis currently skipped on Linux because the Linux implementation used to be missing, buttool_linux.gonow implementsgenerateCommandLine. Leaving the skip in place means the new Linux code path is untested, and the test still hard-codesexeNamefromCOMSPEC(empty on Linux), which would fail if the skip is removed later.
- clipboard.go: guard user.BasicAuth != nil for consistency with other call sites - ssms.go/ssms_unix.go: drop '(Windows only)' suffix from Short text so it matches existing catalog entry - ssms_unix.go: drop localizer wrap on the Windows-only fatal stub message (string is unique to !windows build and not in the catalog)
- vscode.go: use %s with err.Error() so gotext extracts the home dir error - ssms_unix.go: wrap the unix-only fatal in localizer.Sprintf - regenerate catalog under GOOS=linux to register the !windows-tagged string
| cmd := t.generateCommandLine(args) | ||
| err := cmd.Run() | ||
|
|
| home, err := os.UserHomeDir() | ||
| if err != nil || home == "" { | ||
| hint := [][]string{ | ||
| {localizer.Sprintf("Set the HOME environment variable"), "export HOME=/your/home"}, | ||
| } | ||
| if runtime.GOOS == "windows" { | ||
| hint = [][]string{ | ||
| {localizer.Sprintf("Set the USERPROFILE environment variable"), `set USERPROFILE=C:\Users\you`}, | ||
| } | ||
| } | ||
| c.Output().FatalWithHintExamples(hint, localizer.Sprintf("Could not resolve home directory: %s", err.Error())) | ||
| } |
| // -C trusts the self-signed cert that local SQL Server containers ship with. | ||
| if isLocalConnection { | ||
| args = append(args, "-C") | ||
| } | ||
|
|
||
| if user != nil && user.AuthenticationType == "basic" && user.BasicAuth != nil { | ||
| // SSMS removed -P in 18+; hand the password off via the clipboard. | ||
| args = append(args, "-U", user.BasicAuth.Username) | ||
| } | ||
|
|
Summary
Adds two new subcommands and removes one that no longer applies:
sqlcmd open vscode— writes a connection profile for the current context into VS Code'ssettings.jsonand launches VS Code on it via thevscode://URL handler (which prompts to install the MSSQL extension on first use if needed).sqlcmd open ssms— launches SSMS connected to the current context (Ssms.exe -S host,port -nosplash [-C] [-U user]with the password handed off via the clipboard).sqlcmd open adsis removed: Azure Data Studio was retired in August 2025.Password handling for VS Code
The VS Code profile includes the SQL password inline in
settings.jsonrather than prompting the user for it.sqlcmd's containers are short-lived local development instances with throwaway credentials, and the goal ofopen vscodeis one-shot, zero-prompt connect. The MSSQL extension migrates the password out ofsettings.jsonand into the OS credential store on first read, so the on-disk window is brief; we accept that trade-off in exchange for not requiring the user to paste a password they did not choose. This applies only to the local-container dev flow; users connecting to real servers should manage credentials through the extension directly.Editor discovery (Windows)
PATH(code/code-insiders) → Inno Setup uninstall registry → default install dirs.--build stable|insidersselects which build to launch; the connection profile is written to that build'ssettings.jsonso the profile and the launched build always match.vswhere -products Microsoft.VisualStudio.Product.Ssms, which finds SSMS 21+ on any drive.--versionpins a major version; values below 21 are rejected (older SSMS releases are legacy-MSI and aren't registered with the VS Installer).Why not
ssms://?An earlier revision used the
ssms://protocol handler. Its grammar only acceptss/a/u/d/h/q/dnand silently drops-C(trust server certificate) and-P. Local container connections need-C, so the URL path could not connect to a fresh local SQL container regardless of SSMS version. That commit was reverted; the argv launch path is what ships.Testing
go build ./...on windows/linux/darwingo test ./internal/tools/tool/... ./cmd/modern/root/open/...settings.jsonrouting.